package org.javamap;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.GridLayout;
import java.awt.event.*;
import java.awt.print.PrinterJob;

import java.util.Iterator;

import javax.print.attribute.standard.*;

import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JToolBar;

import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import org.javamap.overlays.*;

/**
  * Main class of JavaMap application.
  */
public class JavaMap extends JFrame
{
	public JavaMap()
	{
		super("JavaMap");
		
		java.awt.Container cp = getContentPane();
		
		//cp.setLayout(new GridLayout(1,1));
		cp.setLayout(new BorderLayout());
		
		// Create terrain toolbar:
		cp.add(terrainToolBar, BorderLayout.WEST);
		Iterator keys = ImageManager.iterateKeys();
		while (keys.hasNext()) {
			String key = keys.next().toString();
			terrainToolBar.add(new SetImageKeyAction(key)).setToolTipText(key);
		}
		
		// Create mode toolbar:
		//JPanel eastPanel = new JPanel();
		//cp.add(eastPanel, BorderLayout.EAST);
		cp.add(modeToolBar, BorderLayout.EAST);
		//eastPanel.add(modeToolBar);
		modeToolBar.add(new IconAction("hand.gif") {
			public void actionPerformed(ActionEvent ae)
			{
				overlayListBox.setVisible(false);
				jmp.setMode(jmp.MODE_TERRAIN);
			}
		}).setToolTipText("Set Terrain");
		modeToolBar.add(new IconAction("river.gif") {
			public void actionPerformed(ActionEvent ae)
			{
				overlayListBox.setVisible(true);
				updateListBox(jmp.getHexMap().iterateOverlays(), "River");
						//getRivers());
				jmp.setMode(jmp.MODE_RIVER);
			}
		}).setToolTipText("Add River");
		modeToolBar.add(new IconAction("road.gif") {
			public void actionPerformed(ActionEvent ae)
			{
				overlayListBox.setVisible(true);
				updateListBox(jmp.getHexMap().iterateOverlays(), "Road");
						//jmp.getHexMap().getRoads());
				jmp.setMode(jmp.MODE_ROAD);
			}
		}).setToolTipText("Add Road");
		modeToolBar.add(new IconAction("trail.gif") {
			public void actionPerformed(ActionEvent ae)
			{
				overlayListBox.setVisible(true);
				updateListBox(jmp.getHexMap().iterateOverlays(), "Trail");
						//jmp.getHexMap().getRoads());
				jmp.setMode(jmp.MODE_TRAIL);
			}
		}).setToolTipText("Add Trail");
		modeToolBar.add(new IconAction("zoom_in.gif") {
			public void actionPerformed(ActionEvent ae)
			{
				overlayListBox.setVisible(false);
				jmp.setMode(jmp.MODE_ZOOMIN);
			}
		}).setToolTipText("Zoom In");
		modeToolBar.add(new IconAction("zoom_out.gif") {
			public void actionPerformed(ActionEvent ae)
			{
				overlayListBox.setVisible(false);
				jmp.setMode(jmp.MODE_ZOOMOUT);
			}
		}).setToolTipText("Zoom Out");
		
		// Create overlay list box:
		overlayListBox.setVisible(false);
		modeToolBar.add(overlayListBox);
		overlayListBox.setModel(new DefaultListModel());
		overlayListBox.addListSelectionListener(new ListSelectionListener() {
			public void valueChanged(ListSelectionEvent lse)
			{
				jmp.setSelectedOverlay(
						(Overlay)overlayListBox.getSelectedValue()
				);
			}
		});
		
		jmp = new JavaMapPane();
		jmp.addOverlayListener(overlayListener);
		
		
		// Construct menus
		JMenuBar jmb=new JMenuBar();
		
		/////  File Menu  /////
		JMenu jm=new JMenu("File");
		jm.setMnemonic('F');
		JMenuItem jmi;

		(jmi=new JMenuItem("New...")).addActionListener(
				new ActionListener() {
			public void actionPerformed(ActionEvent ae) 
			{ doNew(); } } );
		jmi.setMnemonic('N');
		jm.add(jmi);

		(jmi=new JMenuItem("Open...")).addActionListener(
				new ActionListener() {
			public void actionPerformed(ActionEvent ae) 
			{ doOpen(); } } );
		jmi.setMnemonic('O');
		jm.add(jmi);
		
		(jmi=new JMenuItem("Save")).addActionListener(
				new ActionListener() {
			public void actionPerformed(ActionEvent ae) 
			{ doSave(); } } );
		jmi.setMnemonic('S');
		jm.add(jmi);

		(jmi=new JMenuItem("Save As...")).addActionListener(
				new ActionListener() {
			public void actionPerformed(ActionEvent ae) 
			{ doSaveAs(); } } );
		jmi.setMnemonic('A');
		jm.add(jmi);
		
		(jmi=new JMenuItem("Print")).addActionListener(
				new ActionListener() {
			public void actionPerformed(ActionEvent ae) 
			{ doPrint(); } } );
		jmi.setMnemonic('P');
		jm.add(jmi);
		
		jm.addSeparator();
		
		(jmi=new JMenuItem("Add Icon")).addActionListener(
				new ActionListener() {
			public void actionPerformed(ActionEvent ae) 
			{ doAddIcon(); } } );
		jmi.setMnemonic('I');
		jm.add(jmi);
		
		(jmi=new JMenuItem("Remove Icon")).addActionListener(
				new ActionListener() {
			public void actionPerformed(ActionEvent ae) 
			{ doRemoveIcon(); } } );
		jmi.setMnemonic('R');
		jm.add(jmi);		

		jm.addSeparator();

		(jmi=new JMenuItem("Exit")).addActionListener(
				new ActionListener() {
			public void actionPerformed(ActionEvent ae) 
			{ doExit(); } } );
		jmi.setMnemonic('x');
		jm.add(jmi);
		
		jmb.add(jm);
		
		/////  Edit Menu  /////
		jm=new JMenu("Edit");
		jm.setMnemonic('E');
		(undoMenu=new JMenuItem("Undo")).addActionListener(
				jmp.getActionMap().get("Undo")
		);
		undoMenu.setMnemonic('u');
		jm.add(undoMenu);
		
		(redoMenu=new JMenuItem("Redo")).addActionListener(
				jmp.getActionMap().get("Redo")
		);
		redoMenu.setMnemonic('r');
		jm.add(redoMenu);
		
		jmb.add(jm);
		
		setJMenuBar(jmb);
		
		
		// Create the map pane and scrolling:
		jmp.addPropertyChangeListener(new JavaMapPanePropertyChangeListener());
		createNewHexMap(2, 2);
		JScrollPane scrollPane = new JScrollPane(jmp);
		vertRuler = new Ruler(Ruler.VERTICAL, jmp);
		scrollPane.setRowHeaderView(vertRuler);
		horizRuler = new Ruler(Ruler.HORIZONTAL, jmp);
		scrollPane.setColumnHeaderView(horizRuler);
		cp.add(scrollPane, BorderLayout.CENTER);
		
		jmp.addDepthListener(new JavaMapPane.DepthListener() {
			public void depthChanged(JavaMapPane jmp)
			{
				changeTitleText();
				jmp.updateRulers(horizRuler, vertRuler);
			}
		});
		
		jmp.updateRulers(horizRuler, vertRuler);
				
		// Set the terrain equal to the first terrain button:
		if (terrainToolBar.getComponentCount() > 0) {
			((JButton) terrainToolBar.getComponent(0)).doClick();
		}
		
		changeTitleText();

		//setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);
		addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent we) 
			{ doExit(); }
		});
		
		setSize(400, 400);
		setLocationRelativeTo(null);
		setVisible(true);
		
		// Set some default page attributes:
		printAttr.add(Chromaticity.COLOR);
	}
	
//******************************  Accessors  ********************************//
	public String getFilename()
	{
		java.io.File f = fileChooser.getSelectedFile();
		if (f == null)
			return "Untitled";
		return f.getName();		
	}
	
///////////////////////////  Private Methods  //////////////////////////////////
	private void changeTitleText()
	{
		int d = jmp.getDepth();
		
		setTitle("JavaMap: "+getFilename()
				+" ("+jmp.getHexMap().getNRows(d)+" rows, "
				+jmp.getHexMap().getNCols(d)+" cols, depth "+d+')');
	}
	
	private void doAddIcon()
	{
		JFileChooser jfc = new JFileChooser("../images");
		
		int res = jfc.showDialog(this, "Select");
		
		if (res == jfc.APPROVE_OPTION) {
			String link = JOptionPane.showInputDialog(this,
					"Enter key to map:");
			if (link != null) {
				ImageManager.addImage(link, jfc.getSelectedFile().getName());
				terrainToolBar.add(new SetImageKeyAction(link))
						.setToolTipText(link);
			}
		}		
	}
	private void doRemoveIcon()
	{
		ImageManager.removeImage(jmp.getImageKey());
		// Remove the button with that key:
		for (int ii = 0; ii < terrainToolBar.getComponentCount(); ++ii) {
			if (jmp.getImageKey().equals(((JComponent)terrainToolBar
						.getComponent(ii)).getToolTipText())) {
				terrainToolBar.remove(ii);
				terrainToolBar.revalidate();
				if (terrainToolBar.getComponentCount() != 0) {
					AbstractButton next = (AbstractButton)
							terrainToolBar.getComponent(0);
					next.requestFocus();
					next.doClick();
					break;
				}
			}
		}
	}
	
	private void createNewHexMap(int rows, int cols)
	{
		jmp.setHexMap(new HexMap(rows, cols, "forest"));
	}
		
	private void doNew()
	{
		String errorTitle = "Error creating new map:";
		
		if (mapSizeDlg.doModal()) {
			try {
				createNewHexMap(mapSizeDlg.getRows(), mapSizeDlg.getCols());
			} catch (NumberFormatException nfe) {
				JOptionPane.showMessageDialog(
						this,
						"Map size invalid.",
						errorTitle,
						JOptionPane.ERROR_MESSAGE
				);
			}
		}
	}
	
	private void doOpen()
	{
		if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
			try {
				jmp.setHexMap(jmp.loadHexMap(fileChooser.getSelectedFile()));
			} catch (Exception ioe) { ioe.printStackTrace(); }
		}			
	}
	
	private void doPrint()
	{
		/* obsolete interface
		java.awt.PrintJob job = getToolkit().getPrintJob(
				this,
				getFilename(),
				jobAttr,
				pageAttr
		);
		
		jmp.paint(job.getGraphics());
		job.end(); */
		
		PrinterJob job = PrinterJob.getPrinterJob();
		job.setPrintable(jmp);
		printAttr.add(new JobName(getFilename(), null)); // default locale
		try {
			if (job.printDialog(printAttr))
				job.print(printAttr);
		} catch (java.awt.print.PrinterException pe) {
			pe.printStackTrace();
		}
	}
	
	private void doSave()
	{
		if (fileChooser.getSelectedFile() == null)
			doSaveAs();
		else try {
			jmp.storeHexMap(fileChooser.getSelectedFile());
		} catch (Exception e) { e.printStackTrace(); }			
	}
	
	private void doSaveAs()
	{
		if (fileChooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
			doSave();
			changeTitleText();
		}
	}
	
	private void doExit()
	{
		dispose();
		jmp.closeHexMap();
	}
	
/*	private void updateListBox(java.util.List overlays)
	{
		DefaultListModel dlm = (DefaultListModel) overlayListBox.getModel();
		dlm.clear();
		for (int ii = 0; ii < overlays.size(); ++ii) {
			dlm.addElement(overlays.get(ii));
		}
	} */
	
	private void updateListBox(Iterator<Overlay> iter, String simpleClass)
	{
		DefaultListModel dlm = (DefaultListModel) overlayListBox.getModel();
		dlm.clear();
		while (iter.hasNext()) {
			Overlay o = iter.next();
			if (o.getClass().getSimpleName().equals(simpleClass))
				dlm.addElement(o);
		}
	}

	
	
/////////////////////////////  Inner Classes  //////////////////////////////////
	private class MapSizeDlg extends JDialog
	{
		MapSizeDlg(JFrame jf)
		{
			super(jf, "New JavaMap", true);
			Container cp = getContentPane();
			cp.setLayout(new GridLayout(3, 2));
			cp.add(new JLabel("Rows: "));
			cp.add(rowBox);
			cp.add(new JLabel("Cols: "));
			cp.add(colBox);
			
			KeyListener kl = new KeyAdapter() {
				public void keyTyped(KeyEvent ke)
				{
					char c = ke.getKeyChar();
					if (c != ke.CHAR_UNDEFINED && !Character.isDigit(c)
							&& c != '\n') {
						ke.consume();
						getToolkit().beep();
					}
				}
			};
			
			rowBox.addKeyListener(kl);
			colBox.addKeyListener(kl);
			
			JButton jb;
			cp.add(jb = new JButton("Ok"));
			jb.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent ae) {
					okHit = true;
					dispose();
				}
			});
			cp.add(jb = new JButton("Cancel"));
			jb.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent ae) {
					dispose();
				}
			});
			
			pack();
			
			setDefaultCloseOperation(DISPOSE_ON_CLOSE);
		}
		
		boolean doModal()
		{
			setLocationRelativeTo(getParent());
			rowBox.setText(String.valueOf(jmp.getHexMap().getNRows()));
			colBox.setText(String.valueOf(jmp.getHexMap().getNCols()));
			okHit = false;
			setVisible(true);
			return okHit;
		}
		
		int getRows() { return Integer.parseInt(rowBox.getText()); }
		int getCols() { return Integer.parseInt(colBox.getText()); }

		private final JTextField rowBox = new JTextField(5),
				colBox = new JTextField(5);
		private boolean okHit;
	}
	
	/**
	  * Creates an icon from a hard-coded filename.
	  */
	private abstract class IconAction extends AbstractAction
	{
		public IconAction(String fname)
		{
			super(
					fname, 
					new javax.swing.ImageIcon(ImageManager.createImage(fname))
			);			
		}
	}
	
	/**
	  * Creates an icon from the database.
	  */
	private class SetImageKeyAction extends AbstractAction
	{
		public SetImageKeyAction(String key)
		{
			super(
					key, 
					new javax.swing.ImageIcon(ImageManager.retrieveImage(key))
			);			
		}			
		
		public void actionPerformed(ActionEvent ae)
		{
			jmp.setImageKey(getValue(NAME).toString());
			jmp.setMode(jmp.MODE_TERRAIN);
			overlayListBox.setVisible(false);
		}		
	}
	
	private class JavaMapPanePropertyChangeListener 
		implements java.beans.PropertyChangeListener
	{
		public void propertyChange(java.beans.PropertyChangeEvent pce)
		{
			String name = pce.getPropertyName(); 
			Object nv = pce.getNewValue();
System.err.println(name + " = " + nv);			
			if ("undoAvailable".equals(name))
				undoMenu.setEnabled(((Boolean) nv).booleanValue());
			else if ("redoAvailable".equals(name))
				redoMenu.setEnabled(((Boolean) nv).booleanValue());		
			else if ("hexMap".equals(name)) {
				overlayListBox.setVisible(false);
				jmp.setMode(jmp.MODE_TERRAIN);
			}
		}
	}
	
/////////////////////////////  Private Data  //////////////////////////////////
	private final JavaMapPane jmp;
	private final JMenuItem undoMenu, redoMenu;
	private final MapSizeDlg mapSizeDlg = new MapSizeDlg(this);
	private final JToolBar 
			terrainToolBar = new JToolBar("Terrain", JToolBar.VERTICAL),
			modeToolBar  = new JToolBar("Modes", JToolBar.VERTICAL);
//			overlayToolBar = new JToolBar("Overlays", JToolBar.VERTICAL);
	private final JList overlayListBox = new JList();
	private final JFileChooser fileChooser = new JFileChooser();
	private final Ruler vertRuler, horizRuler;
	
	// Printing objects:
	private final javax.print.attribute.HashPrintRequestAttributeSet printAttr
			= new javax.print.attribute.HashPrintRequestAttributeSet();
			
	private final OverlayListener overlayListener = new OverlayListener() {
		public void overlayAdded(
				HexMap hm,
				String type,
				java.util.List overlays,
				int index)
		{
			overlayListBox.setVisible(true);
			updateListBox(hm.iterateOverlays(), type); // overlays);
			overlayListBox.setSelectedIndex(index);
		}
				
		public void overlayRemoved(
				HexMap hm,
				String type,
				java.util.List overlays,
				Overlay removed)
		{
			//updateListBox(overlays);
			updateListBox(hm.iterateOverlays(), type);			
		}
	};

////////////////////////////////  Statics  ////////////////////////////////////
	public static void main(String[] args)
	{
		new JavaMap();
	}
}

